/**
* Copyright (C) 2008 by The Regents of the University of California
* Redistribution of this file is permitted under the terms of the GNU
* Public License (GPL).
*
* @author Junghoo "John" Cho <cho AT cs.ucla.edu>
* @date 3/24/2008
*/

#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
	fprintf(stdout, "Bruinbase> ");

	// set the command line input and start parsing user input
	sqlin = commandline;
	sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
	// SqlParser.y by bison (bison is GNU equivalent of yacc)

	return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning

	RC     rc;
	int    key;     
	string value;
	int    count;
	int    diff;
	bool check = false;
	BTreeIndex index;
	char *max = NULL;
	char *min = NULL;
	int counteq = 0;
	int equal = -1;
	int countneq = 0;
	int neq = -1;
	int checkval = 0;
	int checkkey = 0;
	// open the table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}
	// scan the table file from the beginning
	rid.pid = rid.sid = 0;
	count = 0;

	if (index.open(table,'r') != RC_FILE_OPEN_FAILED)
	{
		check = true;
	}

	if(check)
	{
		vector<SelCond> newcond;
		IndexCursor low;
		IndexCursor high;
		SelCond temp;
		for (unsigned i = 0; i < cond.size(); i++) 
		{
			char* condval = cond[i].value;
			switch(cond[i].attr) 
			{
			case 1:
				switch (cond[i].comp) 
				{
				case SelCond::EQ:
					for(int j = i+1; j < cond.size();j++)
					{
						if(cond[j].comp == SelCond::EQ && atoi(cond[i].value) == atoi(cond[j].value))
							break;
					}
					newcond.push_back(cond[i]);
					break;
				case SelCond::NE:
					for(int j = i+1; j < cond.size();j++)
					{
						if(cond[j].comp == SelCond::NE && atoi(cond[i].value) == atoi(cond[j].value))
							break;
					}
					newcond.push_back(cond[i]);
					countneq++;
					break;
				case SelCond::GT:
					if (max == NULL || atoi(max) < atoi(condval)) 
					{
						max = condval;
					}
					break;
				case SelCond::LT:
					if (min == NULL || atoi(min) > atoi(condval)) 
					{
						min = condval;
					}
					break;
				case SelCond::GE:
					if (max == NULL || atoi(max) < atoi(condval) -1) 
					{
						free(max);
						max = (char*)malloc(strlen(condval) +1);
					}
					break;
				case SelCond::LE:
					if (min == NULL || atoi(min) > atoi(condval) +1) 
					{
						free(min);
						min = (char*)malloc(strlen(condval) +1);
					}
					break;
				}
				checkkey++;
				break;
			case 2:
				checkval = 1;
				newcond.push_back(cond[i]);
				break;
			}
		}
		if((checkkey == 0 && (attr==2 || attr==3)) || (checkval !=0 && checkkey==0) || (countneq >= 1 && (checkkey-countneq)==0))
		{
			neq = 1;
			goto skip;
		}
		if(max != NULL && min != NULL && atoi(max) >= atoi(min))
		{
			goto last;
		}
		else
		{
			if(max != NULL)
			{
				temp.attr = 1;
				temp.comp = SelCond::GT;
				temp.value = max;
				newcond.push_back(temp);
			}
			if(min != NULL)
			{
				temp.attr = 1;
				temp.comp = SelCond::LT;
				temp.value = min;
				newcond.push_back(temp);
			}
		}

		for (unsigned i = 0; i < newcond.size(); i++) 
		{
			int condval = atoi(newcond[i].value);
			if(newcond[i].attr == 1)
			{
				if(newcond[i].comp == SelCond::EQ)
				{
					counteq++;
					equal = condval;
				}
			}
		}
		if (neq != 1)
		{
			if(counteq > 1)
			{
				goto last;
			}
			else
			{
				if(newcond.size() == 0)
				{
					index.locate(0,low);
				}
				if(counteq == 0 || counteq == 1)
				{
					if(max != NULL && min != NULL)
					{
						if(equal < atoi(min) && equal > atoi(max) && counteq == 1)
						{
							index.locate(equal-1,low);
							index.locate(equal+1,high);
						}
						else if(atoi(min) < atoi(max))
						{
							goto last;
						}
						else if(atoi(max) < atoi(min))
						{
							index.locate(atoi(max),low);
							index.locate(atoi(min),high);
						}
					}
					else if(max != NULL)
					{
						if(equal > atoi(max) && counteq == 1)
						{
							index.locate(equal-1,low);
							index.locate(equal+1,high);
						}
						else
							index.locate(atoi(max),low);
					}
					else if (min != NULL)
					{
						if(equal < atoi(min) && equal >= 0 && counteq == 1)
						{
							index.locate(equal-1,low);
							index.locate(equal+1,high);
						}
						else
						{
							index.locate(atoi(min),high);
							index.locate(0,low);
						}
					}
					else if (counteq == 1)
					{
						index.locate(equal-1,low);
						index.locate(equal+1,high);
					}
					while(!(high.pid == low.pid && high.eid == low.eid) && low.pid != RC_END_OF_TREE)
					{
						index.readForward(low,key,rid);

						if(checkval == 0 && (attr == 1 || attr == 4))
						{
							for (unsigned i = 0; i < newcond.size(); i++) 
							{
								// compute the difference between the tuple value and the condition value
								switch (newcond[i].attr) 
								{
								case 1:
									diff = key - atoi(newcond[i].value);
									break;
								}

								// skip the tuple if any condition is not met
								switch (newcond[i].comp) 
								{
								case SelCond::EQ:
									if (diff != 0) 
										goto next_tuple1;
									break;
								case SelCond::NE:
									if (diff == 0) 
										goto next_tuple1;
									break;
								case SelCond::GT:
									if (diff <= 0) 
										goto next_tuple1;
									break;
								case SelCond::LT:
									if (diff >= 0) 
										goto next_tuple1;
									break;
								case SelCond::GE:
									if (diff < 0) 
										goto next_tuple1;
									break;
								case SelCond::LE:
									if (diff > 0) 
										goto next_tuple1;
									break;
								}
							}
						}
						else
						{

							rf.read(rid, key, value);
							for (unsigned i = 0; i < newcond.size(); i++) 
							{
								// compute the difference between the tuple value and the condition value
								switch (newcond[i].attr) 
								{
								case 1:
									diff = key - atoi(newcond[i].value);
									break;
								case 2:
									diff = strcmp(value.c_str(), newcond[i].value);
									break;
								}

								// skip the tuple if any condition is not met
								switch (newcond[i].comp) 
								{
								case SelCond::EQ:
									if (diff != 0) 
										goto next_tuple1;
									break;
								case SelCond::NE:
									if (diff == 0) 
										goto next_tuple1;
									break;
								case SelCond::GT:
									if (diff <= 0) 
										goto next_tuple1;
									break;
								case SelCond::LT:
									if (diff >= 0) 
										goto next_tuple1;
									break;
								case SelCond::GE:
									if (diff < 0) 
										goto next_tuple1;
									break;
								case SelCond::LE:
									if (diff > 0) 
										goto next_tuple1;
									break;
								}
							}
						}
						// the condition is met for the tuple. 
						// increase matching tuple counter
						count++;

						// print the tuple 
						switch (attr) 
						{
						case 1:  // SELECT key
							fprintf(stdout, "%d\n", key);
							break;
						case 2:  // SELECT value
							fprintf(stdout, "%s\n", value.c_str());
							break;
						case 3:  // SELECT *
							fprintf(stdout, "%d '%s'\n", key, value.c_str());
							break;
						}
						next_tuple1:
						;
					}
				}
			}
		}
	}
skip:
	;
	if(!check || neq == 1)
	{

		// scan the table file from the beginning
		rid.pid = rid.sid = 0;
		count = 0;

		while (rid < rf.endRid())
		{
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0)
			{
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}

			// check the conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) 
			{
				// compute the difference between the tuple value and the condition value
				switch (cond[i].attr) 
				{
				case 1:
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					diff = strcmp(value.c_str(), cond[i].value);
					break;
				}

				// skip the tuple if any condition is not met
				switch (cond[i].comp) 
				{
				case SelCond::EQ:
					if (diff != 0) 
						goto next_tuple;
					break;
				case SelCond::NE:
					if (diff == 0) 
						goto next_tuple;
					break;
				case SelCond::GT:
					if (diff <= 0) 
						goto next_tuple;
					break;
				case SelCond::LT:
					if (diff >= 0) 
						goto next_tuple;
					break;
				case SelCond::GE:
					if (diff < 0) 
						goto next_tuple;
					break;
				case SelCond::LE:
					if (diff > 0) 
						goto next_tuple;
					break;
				}
			}

			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;

			// print the tuple 
			switch (attr) 
			{
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
			}

			// move to the next tuple
			next_tuple:
			++rid;
		}
	}
last:
	// print matching tuple count if "select count(*)"
	if (attr == 4) 
	{
		fprintf(stdout, "%d\n", count);
	}
	rc = 0;

	// close the table file and return
exit_select:
	rf.close();
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	RecordFile rf;   
	RecordId   rid;
	RC     rc;
	int    key;     
	string value;
	string line;
	ifstream input;
	// open the table file
	input.open (loadfile.c_str());
	if (!input)
		fprintf(stderr, "Unable to open file %s", loadfile.c_str());

	if ((rc = rf.open(table + ".tbl", 'w')) < 0) 
	{
		fprintf(stderr, "Error: table %s cannot be opened\n", table.c_str());
		return rc;
	}
	if (index == false)
	{
		getline(input,line);
		while(input.good() && !input.eof()){
			parseLoadLine(line,key,value);
			rf.append(key,value,rid);	
			getline(input,line);
		}
	}
	else
	{
		BTreeIndex newIndex;
		newIndex.open(table,'w');
		getline(input,line);
		while(input.good() && !input.eof()){
			parseLoadLine(line,key,value);
			rf.append(key,value,rid);	
			newIndex.insert(key,rid);
			getline(input,line);
		}
		newIndex.close();
	}

	rf.close();
	input.close();  
	return 0;

}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
	const char *s;
	char        c;
	string::size_type loc;

	// ignore beginning white spaces
	c = *(s = line.c_str());
	while (c == ' ' || c == '\t') { c = *++s; }

	// get the integer key value
	key = atoi(s);

	// look for comma
	s = strchr(s, ',');
	if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

	// ignore white spaces
	do { c = *++s; } while (c == ' ' || c == '\t');

	// if there is nothing left, set the value to empty string
	if (c == 0) { 
		value.erase();
		return 0;
	}

	// is the value field delimited by ' or "?
	if (c == '\'' || c == '"') {
		s++;
	} else {
		c = '\n';
	}

	// get the value string
	value.assign(s);
	loc = value.find(c, 0);
	if (loc != string::npos) { value.erase(loc); }

	return 0;
}



